﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Diagnostics;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.ComponentModel;
using System.Windows.Threading;

namespace QuickZip.UserControls
{
	public static class SelectionHelper
	{
		#region Attached Properties
		public static DependencyProperty EnableSelectionProperty =
			DependencyProperty.RegisterAttached("EnableSelection", typeof(bool), typeof(SelectionHelper),
			new PropertyMetadata(OnEnableSelectionpChanged));

		public static bool GetEnableSelection(DependencyObject target)
		{
			return (bool)target.GetValue(EnableSelectionProperty);
		}

		public static void SetEnableSelection(DependencyObject target, bool value)
		{
			target.SetValue(EnableSelectionProperty, value);
		}

		private static DependencyProperty LastScrollContentPresenterProperty =
			DependencyProperty.RegisterAttached("LastScrollContentPresenter", typeof(ScrollContentPresenter), typeof(SelectionHelper));

		public static ScrollContentPresenter GetLastScrollContentPresenter(DependencyObject target)
		{
			return (ScrollContentPresenter)target.GetValue(LastScrollContentPresenterProperty);
		}

		public static void SetLastScrollContentPresenter(DependencyObject target, ScrollContentPresenter value)
		{
			target.SetValue(LastScrollContentPresenterProperty, value);
		}

		private static DependencyProperty SelectionAdornerProperty =
			DependencyProperty.RegisterAttached("SelectionAdorner", typeof(SelectionAdorner), typeof(SelectionHelper));

		public static SelectionAdorner GetSelectionAdorner(DependencyObject target)
		{
			return (SelectionAdorner)target.GetValue(SelectionAdornerProperty);
		}

		public static void SetSelectionAdorner(DependencyObject target, SelectionAdorner value)
		{
			target.SetValue(SelectionAdornerProperty, value);
		}

		private static DependencyProperty StartScrollbarPositionProperty =
			DependencyProperty.RegisterAttached("StartScrollbarPosition", typeof(Point), typeof(SelectionHelper));

		public static Point GetStartScrollbarPosition(DependencyObject target)
		{
			return (Point)target.GetValue(StartScrollbarPositionProperty);
		}

		public static void SetStartScrollbarPosition(DependencyObject target, Point value)
		{
			target.SetValue(StartScrollbarPositionProperty, value);
		}

		private static DependencyProperty StartPositionProperty =
			DependencyProperty.RegisterAttached("StartPosition", typeof(Point), typeof(SelectionHelper));

		public static Point GetStartPosition(DependencyObject target)
		{
			return (Point)target.GetValue(StartPositionProperty);
		}

		public static void SetStartPosition(DependencyObject target, Point value)
		{
			target.SetValue(StartPositionProperty, value);
		}

		private static DependencyProperty IsDraggingProperty =
		   DependencyProperty.RegisterAttached("IsDragging", typeof(bool), typeof(SelectionHelper), new PropertyMetadata(false));

		public static bool GetIsDragging(DependencyObject target)
		{
			return (bool)target.GetValue(IsDraggingProperty);
		}
		public static bool GetIsDragging(ListView sender)
		{
			ItemsPresenter ip = UITools.FindVisualChild<ItemsPresenter>(sender);
			ScrollContentPresenter p = UITools.FindAncestor<ScrollContentPresenter>(ip);
			return GetIsDragging(p);
		}
		public static void SetIsDragging(DependencyObject target, bool value)
		{
			target.SetValue(IsDraggingProperty, value);
		}



		public static void OnEnableSelectionpChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
		{
			if (s is ListView)
			{
				ListView control = s as ListView;


				var OnScrollChange = (RoutedEventHandler)delegate(object sender, RoutedEventArgs args)
				{
					ItemsPresenter ip = UITools.FindVisualChild<ItemsPresenter>(sender as ListView);
					ScrollContentPresenter p = UITools.FindAncestor<ScrollContentPresenter>(ip);

					if (GetIsDragging(p) && Mouse.LeftButton == MouseButtonState.Pressed)
						UpdatePosition(p, true);
				};

				var OnMouseDown = (RoutedEventHandler)delegate(object sender, RoutedEventArgs args)
				{
					(sender as ListView).SelectedItems.Clear();
				};

				var OnSizeChanged = (RoutedEventHandler)delegate(object sender, RoutedEventArgs args)
				{
					ItemsPresenter ip = UITools.FindVisualChild<ItemsPresenter>(sender as ListView);
					ScrollContentPresenter p = UITools.FindAncestor<ScrollContentPresenter>(ip);
					EndDragging(p);
				};

				Action unloadAdorner = () =>
					{
						ScrollContentPresenter p = GetLastScrollContentPresenter(control);
						if (p != null)
						{
							SelectionAdorner _adorner = GetSelectionAdorner(p);
							if (AdornerLayer.GetAdornerLayer(p) != null)
								AdornerLayer.GetAdornerLayer(p).Remove(_adorner);

							control.MouseUp -= new MouseButtonEventHandler(OnMouseUp);
							control.MouseMove -= new MouseEventHandler(OnMouseMove);
							_adorner.PreviewMouseDown -= new MouseButtonEventHandler(OnPreviewMouseDown);
							_adorner.MouseMove -= new MouseEventHandler(OnMouseMove);
							_adorner.MouseUp -= new MouseButtonEventHandler(OnMouseUp);
							control.RemoveHandler(ListView.SizeChangedEvent, OnSizeChanged);

							SetSelectionAdorner(p, null);
						}

					};

				Action attachAdorner = () =>
				{
					unloadAdorner();
					ItemsPresenter ip = UITools.FindVisualChild<ItemsPresenter>(control);
					ScrollContentPresenter p = UITools.FindAncestor<ScrollContentPresenter>(ip);
					if (p != null)
					{
						SelectionAdorner _adorner = new SelectionAdorner(p);
						SetSelectionAdorner(p, _adorner);

						AdornerLayer.GetAdornerLayer(p).Add(_adorner);
						control.PreviewMouseDown += new MouseButtonEventHandler(OnPreviewMouseDown);
						control.MouseUp += new MouseButtonEventHandler(OnMouseUp);
						control.MouseMove += new MouseEventHandler(OnMouseMove);
						_adorner.PreviewMouseDown += new MouseButtonEventHandler(OnPreviewMouseDown);
						_adorner.MouseMove += new MouseEventHandler(OnMouseMove);
						_adorner.MouseUp += new MouseButtonEventHandler(OnMouseUp);
						control.AddHandler(ListView.SizeChangedEvent, OnSizeChanged);

						SetLastScrollContentPresenter(control, p);
					}
				};

				if ((bool)e.NewValue == true)
				{
					if (control.IsLoaded)
						attachAdorner();
					else
						control.Loaded += delegate { attachAdorner(); };

					control.AddHandler(ScrollViewer.ScrollChangedEvent, OnScrollChange);
					control.AddHandler(ListView.MouseDownEvent, OnMouseDown);


					//Monitor view change, and reattach handlers.
					DependencyPropertyDescriptor viewDescriptor = DependencyPropertyDescriptor.FromProperty(ListView.ViewProperty, typeof(ListView));

					viewDescriptor.AddValueChanged
					  (control, delegate
					  {
						  control.Dispatcher.BeginInvoke(DispatcherPriority.Input, attachAdorner);
					  });
				}
				else //If EnableSelection = False
				{
					unloadAdorner();
					control.RemoveHandler(ScrollViewer.ScrollChangedEvent, OnScrollChange);
					control.RemoveHandler(ListView.MouseDownEvent, OnMouseDown);

					SetSelectionAdorner(control, null);
				}

			}
		}
		#endregion

		#region Methods

		static ListViewItem getSelectedItem(ScrollContentPresenter lvSender, Point position)
		{
			HitTestResult r = VisualTreeHelper.HitTest(lvSender, position);
			if (r == null) return null;

			DependencyObject obj = r.VisualHit;
			while (!(obj is ListView) && (obj != null))
			{
				obj = VisualTreeHelper.GetParent(obj);

				if (obj is ListViewItem)
					return obj as ListViewItem;
			}

			return null;
		}

		static Point GetScrollbarPosition(ScrollContentPresenter p)
		{
			ScrollViewer scrollViewer = UITools.FindAncestor<ScrollViewer>(p);
			return new Point(p.ActualWidth / scrollViewer.ViewportWidth * scrollViewer.HorizontalOffset,
				p.ActualHeight / scrollViewer.ViewportHeight * scrollViewer.VerticalOffset);
		}

		static void BeginDragging(ScrollContentPresenter p)
		{
			SetStartScrollbarPosition(p, GetScrollbarPosition(p));
			SetStartPosition(p, Mouse.GetPosition(p));
			SetIsDragging(p, true);
		}

		internal static object PositionCheck(DependencyObject sender, object value)
		{
			if (value is Point)
			{
				Point ptValue = (Point)value;
				SelectionAdorner adorner = sender as SelectionAdorner;
				ptValue.X = Math.Min(Math.Max(ptValue.X, 0), adorner.ActualWidth);
				ptValue.Y = Math.Min(Math.Max(ptValue.Y, 0), adorner.ActualHeight);
				return ptValue;
			}
			return value;
		}

		static void UpdateSelectedItems(ListView lvControl, IList<object> newList)
		{
			List<object> addList = new List<object>();
			List<object> removeList = new List<object>(lvControl.SelectedItems as IList<object>);

			foreach (object obj in newList)
				if (removeList.Contains(obj))
					removeList.Remove(obj);
				else addList.Add(obj);

			foreach (object obj in removeList)
				lvControl.SelectedItems.Remove(obj);
			foreach (object obj in addList)
				lvControl.SelectedItems.Add(obj);
		}


		static void HighlightItems(ListView lvControl, IList<int> newList)
		{
			for (int i = 0; i < lvControl.Items.Count; i++)
			{
				ListViewItem item = lvControl.ItemContainerGenerator.ContainerFromIndex(i) as ListViewItem;
				if (item != null)
					SetIsDragging(item, newList.Contains(i));
			}
		}

		static void UnhighlightItems(ListView lvControl)
		{
			for (int i = 0; i < lvControl.Items.Count; i++)
			{
				ListViewItem item = lvControl.ItemContainerGenerator.ContainerFromIndex(i) as ListViewItem;
				if (item != null)
					SetIsDragging(item, false);
			}
		}

		static void UpdateSelection(ScrollContentPresenter p, Rect selectionBounds, bool highlightOnly)
		{
			ListView lvControl = UITools.FindAncestor<ListView>(p);
			if (lvControl != null)
			{
				IChildInfo icInfo = UITools.FindVisualChild<Panel>(p) as IChildInfo;

				List<object> newList = new List<object>();
				List<int> newIntList = new List<int>();

				if (icInfo != null)
				{
					for (int i = 0; i < lvControl.Items.Count; i++)
						if (icInfo.GetChildRect(i).IntersectsWith(selectionBounds))
						{
							newList.Add(lvControl.Items[i]);
							newIntList.Add(i);
						}
				}
				else
					if (lvControl.View is GridView)
					{
						Point curPos = (Point)PositionCheck(GetSelectionAdorner(p), Mouse.GetPosition(p));

						//selectionBounds
						if (_firstSelectedItem == null)
							_firstSelectedItem = getSelectedItem(p, curPos);
						ListViewItem curSelectedItem = getSelectedItem(p, curPos);

						if (_firstSelectedItem != null && curSelectedItem != null)
						{

							int startIdx = lvControl.ItemContainerGenerator.IndexFromContainer(_firstSelectedItem);
							int endIdx = lvControl.ItemContainerGenerator.IndexFromContainer(curSelectedItem);


							for (int i = Math.Min(startIdx, endIdx); i <= Math.Max(startIdx, endIdx); i++)
							{
								//ListViewItem item = lvControl.ItemContainerGenerator.ContainerFromIndex(i) as ListViewItem;
								//if (lvControl.Items[i] is IExtendedListViewContentItem && item != null)
								//{
								//    ExtendedListViewContentItem exItem = lvControl.Items[i] as ExtendedListViewContentItem;
								//    exItem.Index = i*i*i;
								//    exItem.Bounds = VisualTreeHelper.GetDescendantBounds(item);
								//}
								if (i == -1) continue;
								newList.Add(lvControl.Items[i]);
								newIntList.Add(i);
							}

						}

					}


				if (highlightOnly)
					HighlightItems(lvControl, newIntList);
				else
				{
					UnhighlightItems(lvControl);
					UpdateSelectedItems(lvControl, newList);
				}

			}
		}

		static void UpdatePosition(ScrollContentPresenter p, bool highlightOnly)
		{
			ScrollViewer scrollViewer = UITools.FindAncestor<ScrollViewer>(p);
			SelectionAdorner _adorner = GetSelectionAdorner(p);

			if (GetIsDragging(p))
			{
				Point startScrollbarPosition = GetStartScrollbarPosition(p);
				Point curScrollbarPosition = GetScrollbarPosition(p);
				Point startPosition = GetStartPosition(p);
				Point curPosition = Mouse.GetPosition(p);

				if (!_adorner.IsSelecting)
				{
					if (Math.Abs(startPosition.X - curPosition.X) > SystemParameters.MinimumHorizontalDragDistance ||
						Math.Abs(startPosition.Y - curPosition.Y) > SystemParameters.MinimumVerticalDragDistance)
					{
						_adorner.IsSelecting = true;
						Mouse.Capture(p);
					}
				}
				else
				{
					Vector offset = Point.Subtract(startScrollbarPosition, curScrollbarPosition);
					_adorner.StartPosition = Point.Add(startPosition, offset);
					_adorner.EndPosition = curPosition;
					UpdateSelection(p, new Rect(
						new Point(startPosition.X + startScrollbarPosition.X, startPosition.Y + startScrollbarPosition.Y),
						new Point(curPosition.X + curScrollbarPosition.X, curPosition.Y + curScrollbarPosition.Y)), highlightOnly);
				}
			}
		}

		static void EndDragging(ScrollContentPresenter p)
		{
			SelectionAdorner _adorner = GetSelectionAdorner(p);
			if (_adorner.IsSelecting)
			{
				UpdatePosition(p, false);
				_adorner.IsSelecting = false;
				SetIsDragging(p, false);
			}
		}

		static bool _itemAlreadySelected = false;
		static ListViewItem _itemUnderMouse = null;
		static ListViewItem _firstSelectedItem = null;

		static ScrollContentPresenter getScrollContentPresenter(object sender)
		{
			if (sender is ListView)
			{
				ItemsPresenter ip = UITools.FindVisualChild<ItemsPresenter>(sender as ListView);
				return UITools.FindAncestor<ScrollContentPresenter>(ip);
			}
			else
				if (sender is SelectionAdorner)
					return (ScrollContentPresenter)((SelectionAdorner)sender).AdornedElement;
				else return (ScrollContentPresenter)sender;
		}

		static void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
		{
			bool isOverScrollBar = UITools.FindAncestor<ScrollBar>(e.OriginalSource as DependencyObject) != null;

			bool spButtonPressed = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
				Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
			ScrollContentPresenter p = getScrollContentPresenter(sender);
			_itemUnderMouse = getSelectedItem(p, e.GetPosition(p));

			if (!spButtonPressed && !isOverScrollBar)
			{
				_firstSelectedItem = _itemUnderMouse;
				_itemAlreadySelected = (_itemUnderMouse != null && _itemUnderMouse.IsSelected);


				if (!_itemAlreadySelected && e.ClickCount == 1)
				{
					BeginDragging(p);
					e.Handled = true;
				}
			}
			else if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
			{
				ListView lvSender = UITools.FindAncestor<ListView>(sender as DependencyObject);
				int startIdx = lvSender.SelectedIndex;
				int endIdx = lvSender.ItemContainerGenerator.IndexFromContainer(_itemUnderMouse);
				if (startIdx != -1 && endIdx != -1)
				{
					lvSender.SelectedItems.Clear();
					for (int i = Math.Min(startIdx, endIdx); i <= Math.Max(startIdx, endIdx); i++)
						lvSender.SelectedItems.Add(lvSender.Items[i]);
					e.Handled = true;
				}

			}
		}

		static void OnMouseMove(object sender, MouseEventArgs e)
		{
			ScrollContentPresenter p = getScrollContentPresenter(sender);

			if (GetIsDragging(p))
				if (e.LeftButton == MouseButtonState.Pressed)
					UpdatePosition(p, true);
		}

		static void OnMouseUp(object sender, MouseButtonEventArgs e)
		{
			ScrollContentPresenter p = getScrollContentPresenter(sender);

			if (GetIsDragging(p))
			{
				EndDragging(p);
				Mouse.Capture(null);
				UITools.FindAncestor<ListView>(p).Focus();

				ListViewItem curSelectedItem = getSelectedItem(p, Mouse.GetPosition(p));
				if (curSelectedItem != null && curSelectedItem.Equals(_itemUnderMouse))
				{
					UITools.FindAncestor<ListBox>(p).SelectedItems.Clear();
					_itemUnderMouse.IsSelected = true;
				}
			}

			if (_firstSelectedItem == null && GetStartPosition(p).Equals(Mouse.GetPosition(p)))
				UITools.FindAncestor<ListBox>(p).SelectedItems.Clear();
			_firstSelectedItem = null;
			SetIsDragging(p, false);


		}

		#endregion

		#region Data


		#endregion
	}
}
